package org.heka.imageproc_forfun;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

import org.opencv.android.Utils;
import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.core.MatOfFloat;
import org.opencv.core.MatOfInt;
import org.opencv.core.MatOfRect;
import org.opencv.core.Rect;
import org.opencv.core.Size;
import org.opencv.core.Point;
import org.opencv.core.Scalar;
import org.opencv.core.CvType;
import org.opencv.imgproc.Imgproc;
import org.opencv.objdetect.CascadeClassifier;
import org.opencv.highgui.Highgui;
import org.opencv.highgui.VideoCapture;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.drawable.Drawable;
import android.util.Log;
import android.view.SurfaceHolder;

class ImageManipulationsView extends SampleCvViewBase {
	private static final String TAG = "HekaFilter::View";
	
	    
    private Mat mRgba;
    private Mat mGray;
    private Mat mIntermediateMat;
    private Mat mHist, mMat0;
    private MatOfInt mChannels[], mHistSize;
    private int mHistSizeNum;
    private MatOfFloat mRanges;
    private Scalar mColorsRGB[], mColorsHue[], mWhilte;
    private Point mP1, mP2;
    float mBuff[];    
    
    private Size mSize0;
    private Size mSizeRgba;
    private Size mSizeRgbaInner;
    
    private Mat mRgbaInnerWindow;
    private Mat mGrayInnerWindow;
    private Mat mBlurWindow;
    private Mat mZoomWindow;
    private Mat mZoomCorner;

    private Mat mSepiaKernel;
    
    private Mat mRGB_red;
    
    private CascadeClassifier   mCascade;
    
    public static Bitmap 			   overlay_bitmap;
    
    private List<Mat> multi_channel;

    public ImageManipulationsView(Context context) {
        super(context);

        mSepiaKernel = new Mat(4, 4, CvType.CV_32F);
        mSepiaKernel.put(0, 0, /* R */0.189f, 0.769f, 0.393f, 0f);
        mSepiaKernel.put(1, 0, /* G */0.168f, 0.686f, 0.349f, 0f);
        mSepiaKernel.put(2, 0, /* B */0.131f, 0.534f, 0.272f, 0f);
        mSepiaKernel.put(3, 0, /* A */0.000f, 0.000f, 0.000f, 1f);
        
        mRGB_red = new Mat(4, 4, CvType.CV_32F);
        mRGB_red.put(0, 0, /* R */1.000f, 1.000f, 1.000f, 0f);
        mRGB_red.put(1, 0, /* G */0.000f, 0.000f, 0.000f, 0f);
        mRGB_red.put(2, 0, /* B */0.000f, 0.000f, 0.000f, 0f);
        mRGB_red.put(3, 0, /* A */0.000f, 0.000f, 0.000f, 1f);
        
        
        //Face Detection - R.raw.lbpcascade_frontalface
        try {
            InputStream is = context.getResources().openRawResource(R.raw.lbpcascade_frontalface);
        	//InputStream is = context.getResources().openRawResource(R.raw.haarcascade_eye);
            File cascadeDir = context.getDir("cascade", Context.MODE_PRIVATE);
            File cascadeFile = new File(cascadeDir, "lbpcascade_frontalface.xml");
            FileOutputStream os = new FileOutputStream(cascadeFile);

            byte[] buffer = new byte[4096];
            int bytesRead;
            while ((bytesRead = is.read(buffer)) != -1) {
                os.write(buffer, 0, bytesRead);
            }
            is.close();
            os.close();

            mCascade = new CascadeClassifier(cascadeFile.getAbsolutePath());
            if (mCascade.empty()) {
                Log.e(TAG, "Failed to load cascade classifier");
                mCascade = null;
            } else
                Log.i(TAG, "Loaded cascade classifier from " + cascadeFile.getAbsolutePath());

            cascadeFile.delete();
            cascadeDir.delete();

        } catch (IOException e) {
            e.printStackTrace();
            Log.e(TAG, "Failed to load cascade. Exception thrown: " + e);
        }
        
        
        
        //drawable -> bitmap :  convert
        /*final Drawable d = getResources().getDrawable(R.drawable.sunglass);
        Bitmap bitmap = Bitmap.createBitmap(d.getIntrinsicWidth(), d.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
        d.draw(canvas);
        
        overlay_bitmap = bitmap;*/
    }

    @Override
    public void surfaceChanged(SurfaceHolder _holder, int format, int width, int height) {
        super.surfaceChanged(_holder, format, width, height);

        synchronized (this) {
            // initialize Mats before usage
            mGray = new Mat();
            mRgba = new Mat();
            mIntermediateMat = new Mat();
            
            mSize0 = new Size();
            mHist = new Mat();
            mChannels = new MatOfInt[] { new MatOfInt(0), new MatOfInt(1), new MatOfInt(2) };
            mHistSizeNum = 25;
            mBuff = new float[mHistSizeNum];
            mHistSize = new MatOfInt(mHistSizeNum);
            mRanges = new MatOfFloat(0f, 256f);
            mMat0  = new Mat();
            mColorsRGB = new Scalar[] { new Scalar(200, 0, 0, 255), new Scalar(0, 200, 0, 255), new Scalar(0, 0, 200, 255) };
            mColorsHue = new Scalar[] {
            		new Scalar(255, 0, 0, 255),   new Scalar(255, 60, 0, 255),  new Scalar(255, 120, 0, 255), new Scalar(255, 180, 0, 255), new Scalar(255, 240, 0, 255),
            		new Scalar(215, 213, 0, 255), new Scalar(150, 255, 0, 255), new Scalar(85, 255, 0, 255),  new Scalar(20, 255, 0, 255),  new Scalar(0, 255, 30, 255),
            		new Scalar(0, 255, 85, 255),  new Scalar(0, 255, 150, 255), new Scalar(0, 255, 215, 255), new Scalar(0, 234, 255, 255), new Scalar(0, 170, 255, 255),
            		new Scalar(0, 120, 255, 255), new Scalar(0, 60, 255, 255),  new Scalar(0, 0, 255, 255),   new Scalar(64, 0, 255, 255),  new Scalar(120, 0, 255, 255),
            		new Scalar(180, 0, 255, 255), new Scalar(255, 0, 255, 255), new Scalar(255, 0, 215, 255), new Scalar(255, 0, 85, 255),  new Scalar(255, 0, 0, 255)
            };
            mWhilte = Scalar.all(255);
            mP1 = new Point();
            mP2 = new Point();
            
        }
    }

    private void CreateAuxiliaryMats() {
        if (mRgba.empty())
            return;
        
        mSizeRgba = mRgba.size(); 

        int rows = mRgba.rows();
        int cols = mRgba.cols();

        int left = cols / 8;
        int top = rows / 8;

        int width = cols * 3 / 4;
        int height = rows * 3 / 4;

        if (mRgbaInnerWindow == null)
            mRgbaInnerWindow = mRgba.submat(top, top + height, left, left + width);
        
        mSizeRgbaInner = mRgbaInnerWindow.size(); 

        if (mGrayInnerWindow == null && !mGray.empty())
            mGrayInnerWindow = mGray.submat(top, top + height, left, left + width);

        if (mBlurWindow == null)
            mBlurWindow = mRgba.submat(0, rows, cols / 3, cols * 2 / 3);

        if (mZoomCorner == null)
        	mZoomCorner = mRgba.submat(0, rows / 2 - rows / 10, cols / 2 - cols / 10, cols / 2 + cols / 10);
                
        if (mZoomWindow == null)
            mZoomWindow = mRgba.submat(rows / 2 - 9 * rows / 100, rows / 2 + 9 * rows / 100, cols / 2 - 9 * cols / 100, cols / 2 + 9 * cols / 100);
        
       /* if (mZoomCorner == null)
        	mZoomCorner = mRgba.submat(0, rows / 2 - rows / 10, cols / 2 - cols / 10, cols / 2 + cols / 10);*/
    }

    @Override
    protected Bitmap processFrame(VideoCapture capture) {
        switch (ImageManipulationsActivity.viewMode) {

        case ImageManipulationsActivity.VIEW_MODE_RGBA:
            capture.retrieve(mRgba, Highgui.CV_CAP_ANDROID_COLOR_FRAME_RGBA);
            break;
            
        case ImageManipulationsActivity.VIEW_MODE_HIST:
            capture.retrieve(mRgba, Highgui.CV_CAP_ANDROID_COLOR_FRAME_RGBA);
            if (mSizeRgba == null)
                CreateAuxiliaryMats();
            int thikness = (int) (mSizeRgba.width / (mHistSizeNum + 10) / 5);
            if(thikness > 5) thikness = 5;
            int offset = (int) ((mSizeRgba.width - (5*mHistSizeNum + 4*10)*thikness)/2);
            // RGB
            for(int c=0; c<3; c++) {
            	Imgproc.calcHist(Arrays.asList(mRgba), mChannels[c], mMat0, mHist, mHistSize, mRanges);
            	Core.normalize(mHist, mHist, mSizeRgba.height/2, 0, Core.NORM_INF);
            	mHist.get(0, 0, mBuff);
            	for(int h=0; h<mHistSizeNum; h++) {
            		mP1.x = mP2.x = offset + (c * (mHistSizeNum + 10) + h) * thikness; 
            		mP1.y = mSizeRgba.height-1;
            		mP2.y = mP1.y - 2 - (int)mBuff[h]; 
            		Core.line(mRgba, mP1, mP2, mColorsRGB[c], thikness);
            	}
            }
            // Value and Hue
            Imgproc.cvtColor(mRgba, mIntermediateMat, Imgproc.COLOR_RGB2HSV_FULL);
            // Value
            Imgproc.calcHist(Arrays.asList(mIntermediateMat), mChannels[2], mMat0, mHist, mHistSize, mRanges);
        	Core.normalize(mHist, mHist, mSizeRgba.height/2, 0, Core.NORM_INF);
        	mHist.get(0, 0, mBuff);
        	for(int h=0; h<mHistSizeNum; h++) {
        		mP1.x = mP2.x = offset + (3 * (mHistSizeNum + 10) + h) * thikness; 
        		mP1.y = mSizeRgba.height-1;
        		mP2.y = mP1.y - 2 - (int)mBuff[h]; 
        		Core.line(mRgba, mP1, mP2, mWhilte, thikness);
        	}
            // Hue
            Imgproc.calcHist(Arrays.asList(mIntermediateMat), mChannels[0], mMat0, mHist, mHistSize, mRanges);
        	Core.normalize(mHist, mHist, mSizeRgba.height/2, 0, Core.NORM_INF);
        	mHist.get(0, 0, mBuff);
        	for(int h=0; h<mHistSizeNum; h++) {
        		mP1.x = mP2.x = offset + (4 * (mHistSizeNum + 10) + h) * thikness; 
        		mP1.y = mSizeRgba.height-1;
        		mP2.y = mP1.y - 2 - (int)mBuff[h]; 
        		Core.line(mRgba, mP1, mP2, mColorsHue[h], thikness);
        	}
            break;


        case ImageManipulationsActivity.VIEW_MODE_SEPIA:
            capture.retrieve(mRgba, Highgui.CV_CAP_ANDROID_COLOR_FRAME_RGBA);
            Core.transform(mRgba, mRgba, mSepiaKernel);
            break;

        case ImageManipulationsActivity.VIEW_MODE_GRAY:
            capture.retrieve(mRgba, Highgui.CV_CAP_ANDROID_GREY_FRAME);
          
            break;

        case ImageManipulationsActivity.VIEW_MODE_PIXELIZE:
        	  capture.retrieve(mRgba, Highgui.CV_CAP_ANDROID_COLOR_FRAME_RGBA);
              if (mRgbaInnerWindow == null)
                  CreateAuxiliaryMats();
              Imgproc.resize(mRgbaInnerWindow, mIntermediateMat, mSize0, 0.1, 0.1, Imgproc.INTER_NEAREST);
              Imgproc.resize(mIntermediateMat, mRgbaInnerWindow, mSizeRgbaInner, 0., 0., Imgproc.INTER_NEAREST);
            break;
           
        case ImageManipulationsActivity.VIEW_MODE_FACE:
        	capture.retrieve(mRgba, Highgui.CV_CAP_ANDROID_COLOR_FRAME_RGBA);
            capture.retrieve(mGray, Highgui.CV_CAP_ANDROID_GREY_FRAME);
                      
            MatOfRect faces = new MatOfRect();            
            
            if (mCascade != null) {
                int height = mGray.rows();
                int faceSize = Math.round(height * ImageManipulationsActivity.minFaceSize);
                              
                mCascade.detectMultiScale(mGray, faces, 1.1, 2, 2 // TODO: objdetect.CV_HAAR_SCALE_IMAGE
                        , new Size(faceSize, faceSize), new Size());
               
                if(ImageManipulationsActivity.list_category_ID == ImageManipulationsActivity.LIST_CALCULATE)
                {
                	// Draw Rect
                      for (Rect r : faces.toArray())
                      {
                      	Core.rectangle(mRgba, r.tl(), r.br(), new Scalar(0, 255, 0, 255), 3);    
                      }         
                }
              
                
			}

            
            //Empty canvas bitmap ready
            Bitmap background = Bitmap.createBitmap(mRgba.cols(), mRgba.rows(), Bitmap.Config.ARGB_8888);            
            Canvas canvas = new Canvas(background);
            Bitmap reisizeBitmap = null;
            
            try {
            	Utils.matToBitmap(mRgba, background);
    			
    			if (faces != null) {
    				// Draw Image
    				
    				for (Rect r : faces.toArray()) {    	
    					
						switch (ImageManipulationsActivity.overlay_point_ID) {
						case ImageManipulationsActivity.OVERLAY_NORMAL_IMAGE:
							reisizeBitmap = Bitmap.createScaledBitmap(overlay_bitmap, r.width, r.height, true);
	    					canvas.drawBitmap(reisizeBitmap, (float) r.tl().x, (float) r.tl().y, null);    					
							break;
					
						case ImageManipulationsActivity.OVERLAY_MASK_IMAGE:
							reisizeBitmap = Bitmap.createScaledBitmap(overlay_bitmap, r.width+20, r.height+205, true);
	    					canvas.drawBitmap(reisizeBitmap, (float) r.tl().x, (float) r.tl().y-170, null);    				
							break;
						
						}
    				}
    			}
    			return background;
            } catch(Exception e) {            	
            	//background.recycle();
            	//reisizeBitmap.recycle();
                return null;
            }
            
           /* overlay_bitmap.recycle();
            background.recycle();
            return null;*/
        }

        Bitmap bmp = Bitmap.createBitmap(mRgba.cols(), mRgba.rows(), Bitmap.Config.ARGB_8888);       
        
        try {
        	  Utils.matToBitmap(mRgba, bmp);
              return bmp;
        } catch(Exception e) {
            //bmp.recycle();
            return null;
        }
    }

    @Override
    public void run() {
        super.run();

        synchronized (this) {
            // Explicitly deallocate Mats
            if (mZoomWindow != null)
                mZoomWindow.release();
            if (mZoomCorner != null)
                mZoomCorner.release();
            if (mBlurWindow != null)
                mBlurWindow.release();
            if (mGrayInnerWindow != null)
                mGrayInnerWindow.release();
            if (mRgbaInnerWindow != null)
                mRgbaInnerWindow.release();
            if (mRgba != null)
                mRgba.release();
            if (mGray != null)
                mGray.release();
            if (mIntermediateMat != null)
                mIntermediateMat.release();

            mRgba = null;
            mGray = null;
            mIntermediateMat = null;
            mRgbaInnerWindow = null;
            mGrayInnerWindow = null;
            mBlurWindow = null;
            mZoomCorner = null;
            mZoomWindow = null;
        }
    }
}